#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import time
import re
from datetime import datetime

from util.tools import get_db_tb_key
from util.tools import format_date

PARSE_MODE = '-vv --base64-output=decode-rows'
PARSE_MODE = '--no-defaults -vv --base64-output=decode-rows'


def is_valid_time_str(s):
    """
        check the input str is valid time str or not
        Args:
            s: the input str to be checked
        Returns:
            valid: True
            unvalid: False
    """
    try:
        time.strptime(s, '%Y-%m-%d %H:%M:%S')
        return True
    except:
        return False


def decode_mysqlbinlog(log_file, s_time='', e_time='', binlog_tool=''):
    """
        decode the mysqlbinlog file based on given time period
        Args:
            log_file: the path of mysqlbinlog file
            s_time: the start time point of to be processed logs
                    the argument must be a date and time in the local time zone
                    for example: 2004-12-25 11:25:56
            e_time: the end time point of to be processd logs
                    the argument must be a date and time in the local time zone
                    for example: 2004-12-25 11:25:56
        Returns:
            success: status == 200
            else: fail
    """
    s_time = s_time.replace('T', ' ').replace('Z', '')
    e_time = e_time.replace('T', ' ').replace('Z', '')
    # check args is valid or not
    MYSQLBINLOG_EXE = binlog_tool
    if not os.path.exists(MYSQLBINLOG_EXE):
        return {'status': 601, 'msg': 'MYSQLBINLOG_EXE not exist'}
    if not os.path.exists(log_file):
        return {'status': 602, 'msg': 'input log_file not exist'}
    if (s_time and not is_valid_time_str(s_time)) or (e_time and not is_valid_time_str(e_time)):
        return {'status': 603, 'msg': 's_time or e_time arg is not valid'}

    # build the decoded exe command
    if not s_time and not e_time:
        command = ' '.join([MYSQLBINLOG_EXE, PARSE_MODE, log_file])
    elif s_time and e_time:
        s_time_arg = '--start-datetime="{0}"'.format(s_time)
        e_time_arg = '--stop-datetime="{0}"'.format(e_time)
        command = ' '.join([MYSQLBINLOG_EXE, PARSE_MODE, s_time_arg, e_time_arg, log_file])
    elif s_time:
        s_time_arg = '--start-datetime="{0}"'.format(s_time)
        command = ' '.join([MYSQLBINLOG_EXE, PARSE_MODE, s_time_arg, log_file])
    else:
        e_time_arg = '--stop-datetime="{0}"'.format(e_time)
        command = ' '.join([MYSQLBINLOG_EXE, PARSE_MODE, e_time_arg, log_file])

    return command


text_type = ['char', 'text', 'string']


def handle_value_line(line, data_pool, database, table):
    mark_start = line.rindex('/*')
    value = line[:mark_start].strip().split('=', 1)
    if len(value) == 2:
        value = value[1]
        if value.startswith('\''):
            value = value[1:-1]
        describe = re.split(r'\s+', line[mark_start:])
        if len(describe) > 1:
            v_type = describe[1].lower()
            if 'nullable=1' in describe and 'is_null=1' in describe:
                value = ''
            elif v_type.find('timestamp') != -1:
                value = time.localtime(float(value))
                value = time.strftime('%Y-%m-%d %H:%M:%S', value)
            elif v_type.find('date') != -1 or v_type.find('time') != -1:
                value = format_date(value.replace(':', '-'))
                value = str(value)
            elif database and table:
                db_tb_key = get_db_tb_key(database, table)
                charset = data_pool[db_tb_key]['db_table_charset']
                for v in text_type:
                    if v_type.find(v) != -1:
                        try:
                            if charset == 'utf8mb4':
                                charset = 'utf8'
                            value = value.decode(charset)
                            return value
                        except Exception, e:
                            value = ''
                        break
    return value


def parse_mysqlbinlog(log_file, s_time='', e_time='', binlog_tool='/usr/bin/mysqlbinlog', global_db={}, data_pool={}):
    """
        input the original mysqlbinlog file, parse it, return the list of wanted sqls
        Args:
            log_file: the path of mysqlbinlog file
            s_time: the start time point of to be processed logs
                    the argument must be a date and time in the local time zone
                    for example: 2004-12-25 11:25:56
            e_time: the end time point of to be processd logs
                    the argument must be a date and time in the local time zone
                    for example: 2004-12-25 11:25:56
            goal_dbs: the db list, you want to focus
            goal_tables: the table list, you want to focus
        Returns:
            fail: {'status' != 200, .....}
            success: sql list
    """
    command_str = decode_mysqlbinlog(log_file, s_time, e_time, binlog_tool)
    if not s_time:
        s_time = '1970-01-01T01:01:01Z'
    flag, row_num, sql_lines, sql_type = 0, 0, [], ''
    alter = False
    use = False
    alter_list = []
    alter_dbname = None
    alter_tablename = None
    for line in os.popen(command_str):
        if line.startswith('BEGIN'):
            flag, is_find, sql_lines, sql_type = 1, False, [], ''
            dbname = None
            tablename = None
        elif flag == 1:
            try:
                if not is_find:
                    dbname, tablename = line.split('`')[1], line.split('`')[3]
                    is_find = True
                    if not dbname or not tablename:
                        flag = 0
                        continue
                    else:
                        if (dbname not in global_db) or (tablename not in global_db[dbname]):
                            flag = 0
                            continue
            except:
                continue
            if line.startswith('###'):
                if not is_find:
                    flag = 0
                    continue
                line = line[3:].strip()
                if not sql_type:
                    if line.startswith('UPDATE'):
                        sql_type = 'UPDATE'
                    elif line.startswith('INSERT'):
                        sql_type = 'INSERT'
                    elif line.startswith('DELETE'):
                        sql_type = 'DELETE'
                if sql_type == "UPDATE":
                    if line.startswith("UPDATE"):
                        if sql_lines:
                            yield {'line_type': 'insert', 'data': sql_lines, 'db_tb': get_db_tb_key(dbname, tablename)}
                        sql_lines = []
                        sql_lines.append("UPDATE")
                    elif line.startswith("@"):
                        if "UPDATE" not in sql_lines:
                            sql_lines.append(handle_value_line(line, data_pool, dbname, tablename))
                    elif line.startswith("SET"):
                        sql_lines = []
                elif sql_type == 'DELETE':
                    if line.startswith("DELETE"):
                        if sql_lines:
                            yield {'line_type': 'delete', 'data': sql_lines, 'db_tb': get_db_tb_key(dbname, tablename)}
                        sql_lines = []
                    elif line.startswith("@"):
                        sql_lines.append(handle_value_line(line, data_pool, dbname, tablename))
                elif sql_type == 'INSERT':
                    if line.startswith("INSERT"):
                        if sql_lines:
                            yield {'line_type': 'insert', 'data': sql_lines, 'db_tb': get_db_tb_key(dbname, tablename)}
                        sql_lines = []
                    elif line.startswith("@"):
                        sql_lines.append(handle_value_line(line, data_pool, dbname, tablename))
            elif line.startswith('COMMIT'):
                if sql_lines:
                    if sql_type.lower() == 'update':
                        sql_type = 'insert'
                    yield {'line_type': sql_type.lower(), 'data': sql_lines, 'db_tb': get_db_tb_key(dbname, tablename)}
        if line.startswith('use'):
            if alter and alter_list and alter_dbname and alter_tablename:
                yield {'line_type': 'alter', 'data': alter_list, 'db_tb': get_db_tb_key(alter_dbname, alter_tablename)}
                alter_list = []
            alter_dbname = None
            alter_tablename = None
            temp_db = line.split('`')
            if len(temp_db) > 2:
                alter_dbname = temp_db[1]
            if alter_dbname in global_db:
                alter = True
            else:
                alter = False
                alter_dbname = None
        elif alter:
            line = line.strip().lower()
            if line.startswith('alter'):
                if alter_tablename:
                    if alter_list:
                        yield {'line_type': 'alter', 'data': alter_list,
                               'db_tb': get_db_tb_key(alter_dbname, alter_tablename)}
                        alter_list = []
                    alter_tablename = None
                temp_tb = line.split('`')
                if len(temp_tb) > 2:
                    alter_tablename = temp_tb[1]
                    if alter_tablename in global_db[alter_dbname]:
                        if len(temp_tb) > 2:
                            deal_type = temp_tb[2].strip()
                            if deal_type == 'add':
                                if temp_tb > 4:
                                    field_type = re.split(r'\(| ', temp_tb[4].strip())[0]
                                else:
                                    field_type = 'string'
                                alter_list.append(('add', temp_tb[3], field_type))
                            elif deal_type == 'drop':
                                alter_list.append(('drop', temp_tb[3]))
                    else:
                        alter_tablename = None
                        continue
            if not alter_tablename or alter_tablename not in global_db[alter_dbname]:
                continue
            if line.startswith('add'):
                temp_tb = line.split('`')
                field_type = re.split(r'\(| ', temp_tb[2].strip())[0]
                alter_list.append(('add', temp_tb[1], field_type))
            elif line.startswith('drop'):
                alter_list.append(('drop', line.split('`')[1]))
            elif line.startswith('change'):
                alter_list.append(('change', line.split('`')[1], line.split('`')[3]))
            elif line.strip() == 'begin':
                if alter_list:
                    yield {'line_type': 'alter', 'data': alter_list,
                           'db_tb': get_db_tb_key(alter_dbname, alter_tablename)}
                    alter_list = []
                alter_tablename = None
        else:
            m = re.match(r'^#\d+\s+\d+:\d+:\d+', line)
            if m:
                new_time = m.group(0).replace('#', '20')
                new_time = datetime.strptime(new_time, '%Y%m%d %H:%M:%S').strftime('%Y-%m-%dT%H:%M:%SZ')
                if new_time > s_time:
                    s_time = new_time
            continue
    try:
        if alter_list:
            yield {'line_type': 'alter', 'data': alter_list, 'db_tb': get_db_tb_key(alter_dbname, alter_tablename)}
    except:
        pass
    yield {'line_type': 'end_time', 'data': s_time}


if __name__ == '__main__':
    # parse_mysqlbinlog('/Users/zhouzheng/Downloads/mysql-bin.000004', '2015-09-22 17:59:12', '2015-09-23 05:46:19')
    sqls = parse_mysqlbinlog('mysql-bin.000042', '2015-10-13 16:07:37', '', ['haizhi'], ['test1'])
    for sql in sqls:
        print sql
